/*
 * Copyright 2010 Emmanuel Roussel
 * http://rousselmanu.free.fr
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street,
 * Boston, MA 02110-1301, USA.
 */

#include <cc1110.h>
#include "ioCCxx10_bitdef.h"
#include "display.h"
#include "keys.h"
#include "5x7.h"
#include "stdio.h"
#include "pm.h"
#include "types.h"
#include "bits.h"
#include "music.h"
#include "tetris.h"
#include "rand.h"

void main(void) {
	u8 i;

reset:
	init_var();		//init global variables
	init_music();
	setup_radio();	//for the random number generator
	xtalClock();	//26MHz
	setIOPorts();	
	//For the sound :
	P1DIR |= BIT0;	//Port 1, bit 0 as an output (piezzoelectric sounder)
	configureSPI();	//for the communication with the LCD screen
	LCDReset();
	clear();		//clear the LCD

	LED_GREEN=0;	//LED green ON (0=ON, 1=OFF)
	
	display_menu();
	beep();
	
	sleepMillis(2000);
	clear();
	init_rand();
	
	draw_board();
	
	play_music(tetris_theme, SCORE_LENGTH);
    
    g_next.type=rand()%7;	//spawn a new random piece
	g_next.rotation=0;
	g_next.col=5;
	g_next.row=18;
    
    SSN=LOW;
	spawn_piece();	//loading the piece
	print_score();
	SSN=HIGH;
	
	setup_game_timer();

//main loop
	while (1) {
		if(!g_game_is_over){		//do not refresh if the game is over : wait for the user to press the power button.
			SSN=LOW;
			for(i=0; i<=17; i++)		//update the screen
				draw_line(i);
			for(i=19; i<=22; i++)
				draw_line(i);
			SSN=HIGH;
		}
			
		poll_keyboard();
		
		// go to sleep (more or less a shutdown) if power button pressed
		if (sleepy) {
			prepare_to_sleep();
			sleep();
			// reset on wake
			sleepy=0;
			goto reset;
		}
	}
}

u8 poll_keyboard() {
	u8 c=getkey();
	switch (c) {
	case KSPK:
		if(is_music_ON())
			stop_music();
		else
			play_music(tetris_theme, SCORE_LENGTH);
		break;
	case 'Z':
		piece_rotate();
		break;
	case '<':
		T1CC0H=0x10;	//increases the falling speed.
		T1CNTL=0;
		break;
	case 'Q':
		piece_left();
		break;
	case KCAP:
		piece_right();
		break;
	case KPWR:
		sleepy = 1;
		break;
	default:
		break;
	}
	return c;
}

void init_var(){
	u8 i;
	g_score=0;
	g_falling_speed=0xFF;
	g_game_is_over=0;
	
	t_pos[0]=0x0FFF;		//ones on the lowest line
	for(i=1; i<=18; i++)
		t_pos[i]=0xF000;	//1 on the right
	for(i=20; i<=23; i++)
		t_pos[i]=0;
}

void prepare_to_sleep(){
	clear();
	LED_GREEN=LED_RED=1;
	sleepMillis(1000);
	SSN = LOW;
	LCDPowerSave();
	SSN = HIGH;
	stop_music();
	T1CTL=0;
}

void game_over(){	
	T1CTL=0;	//stops the game timer
	stop_music();
	beep();
	SSN=LOW;
	setNormalReverse(1);
	setCursor(3, 35);
	printf(" GAME OVER! ");
	setCursor(5, 25);
	printf(" score: %d ", g_score);
	SSN=HIGH;
	g_game_is_over=1;
}

//------------------------------------------------------------------------------------

void print_number(u8 nb){	//prints a number "vertically". Could easily be improved to print any character vertically. I will do it for next version, so as to display "game over" for example.
	u8 j, buf;
	char i;
	if(nb>9) return;
	for(i=7; i>=0; i--){	//go through all the bits
		buf=0;
		for(j=0; j<5; j++)	//go through all the bytes
			buf|=((number[nb][j]>>i)&1)<<j;	//pick up one bit in each byte and fill "buf" with them
		txData(buf);
	}
}

void print_score(){
	u8 i=8;
	u16 tmp=g_score;
	do{
		i--;
		setCursor(i,122);
		print_number((u8)(tmp%10)&0xFF);
	}while (((u16)(tmp /= 10) > 0) && i>0);
}

void display_menu(void){
	SSN=LOW;
	setDisplayStart(0);
	setNormalReverse(0);
	setCursor(1, 40);	//row (0 to 9), col
	printf("TETRIS-ME");	
	setCursor(5, 15);
	printf("Emmanuel Roussel");
	SSN=HIGH;
}

//------------------------------------------------------------------------------------


u8 check_collision(){	//to be called BEFORE calling draw_current !! but after having changed a value in g_cur.
	u8 i;
	for(i=0; i<4; i++){
		if(t_pos[g_cur.row-i] & (list_pieces[g_cur.type][g_cur.rotation][i]<<g_cur.col) !=0)	//check for collision (1 in t_pos AND 1 in list_piece at the same place)
			return 1;	//collision found
	}
	return 0;
}

void check_line(){	//checks for lines, clears it and updates the score
	u8 i,j;
	u8 nb_lines=0;
	for(i=1; i<=17; i++){	//through all the lines but the last
		if((t_pos[i]&0x0FFF) == 0x0FFF){	//if the line is full
			LED_RED=0;
			nb_lines++;
			t_pos[18]=0xF000;
			for(j=i; j<=17; j++)	//clears the line
				t_pos[j]=t_pos[j+1];
			i--;	//we recheck this same line 
		}
	}
	if(nb_lines>0){
		g_score+=nb_lines*10+(nb_lines-1)*5;	//1 line = 10 points, 2 lines = 25p, 3l = 40, 4l = 55
		print_score();
		g_falling_speed=255-(g_score>>2);		//change the falling speed according to the score.
		if(g_falling_speed<0x26) g_falling_speed=0x26;	//at score=870
	}
}

//------------------------------------------------------------------------------------

void spawn_piece(){
	check_line();
	T1CC0H=g_falling_speed;	//reset the falling speed
	
	clear_piece(g_next.type, g_next.rotation, 5, 23);
	
	g_cur.type=g_next.type;
	g_cur.rotation=g_next.rotation;
	g_cur.col=g_next.col;
	g_cur.row=g_next.row;
	
	g_next.type=rand()%7;	//spawn a new random piece
	g_next.rotation=0;
	g_next.col=5;
	g_next.row=18;
	
	draw_piece(g_next.type, g_next.rotation, 5, 23);
	
	if(check_collision())	//check if the game is over
		game_over();

	draw_current();	//draw the new piece
}

//------------------------------------------------------------------------------------

void draw_piece(u8 piece, u8 rotation, u8 col, u8 row){	//row € [0 (down), 17 (up)]. col € [0 (left), 11 (right)]. Don't actually draw the piece on the screen: only updates t_pos
	u8 i;
	for(i=0; i<4; i++)
		t_pos[row-i] |= list_pieces[piece][rotation][i]<<col;
}

void draw_current(){
	draw_piece(g_cur.type, g_cur.rotation, g_cur.col, g_cur.row);
}

void clear_piece(u8 piece, u8 rotation, u8 col, u8 row){
	u8 i;
	for(i=0; i<4; i++){
		t_pos[row-i] &= ~(list_pieces[piece][rotation][i]<<col);
		t_pos[row-i] |= 0xF000;	//write the ones on the right
	}
}

void clear_current(){
	clear_piece(g_cur.type, g_cur.rotation, g_cur.col, g_cur.row);
}


//redraw the whole line according to the content of t_pos[].
void draw_line(u8 line){	//line is between 0 (down) and 17 (up)
	u8 i,j;
	u16 buf16=1;	//buffer. 1 for the left limit.
	u8 index=2;		//offset : 2 bits on the right for the limit.
	u8 box_no=0;	//the number of the current box (between 0 and 11)
	
	for(i=0; i<8; i++){	//going through all the columns of the LCD (=lines in the normal position!). 8 columns which needs 8bits
		while(index<=7){	//while the 8 bits of this byte (=for this column) are not set		
			if(t_pos[line+1]&(1<<box_no++) !=0)
				buf16|=(0x000F)<<index;	//if there isa box on the current place, put 0b1111 at the right place
			index+=5;							//5 because of the four ones + 0
		}
		setCursor(i, line*5+2);
		if(i==7){
			buf16|=1<<6;	//draw the right limit: 1
			buf16&=0x7F;	//and 0
		}
		for(j=0; j<4; j++) txData((u8)(buf16&0xFF));	//put the 8 LSB bits
		index-=8;
		buf16=buf16>>8;	//if a box is between two 8bit-columns, index is >0 and bu16>>8 contains the remaining 1s.
	}
}

void draw_board(void){
	u8 i;
	SSN=LOW;
	setCursor(0,0);	//go to down left
	txData(0xFF);
	txData(1);
	for(i=0; i<90; i++) txData(1);	//90=18*5 : 18 lines
	txData(0xFF);
	
	for(i=1; i<7; i++){
		setCursor(i,0);
		txData(0xFF);
		setCursor(i,92);
		txData(0xFF);
	}
	
	setCursor(7,0);
	txData(0x7F);
	txData(0x40);
	for(i=0; i<90; i++) txData(0x40);
	txData(0x7F);
	SSN=HIGH;
}

//------------------------------------------------------------------------------------

void piece_down(){
	clear_current();
	g_cur.row--;
	if(check_collision()){	//if there is a collision with the new position
		g_cur.row++;		//undo
		draw_current();		//redraw
		SSN=LOW;
		spawn_piece();
		SSN=HIGH;
		return;
	}
	draw_current();
}

void piece_left(){
	if(g_cur.col==0) return;
	clear_current();
	g_cur.col--;
	if(check_collision()){
		g_cur.col++;
	}
	draw_current();
}

void piece_right(){
	clear_current();
	g_cur.col++;
	if(check_collision()){	//undo
		g_cur.col--;
	}
	draw_current();
}

void piece_rotate(){
	clear_current();
	g_cur.rotation++;
	if(g_cur.rotation>=4) g_cur.rotation=0;
	if(check_collision()){	//if a collision occured, undo the rotation
		g_cur.rotation--;
	}
	draw_current();
}

//------------------------------------------------------------------------------------

void timer1_isr() __interrupt (T1_VECTOR){
	LED_RED=1;
	piece_down();

    T1IF = 0;
}

void setup_T1(void){
    T1CC0H = 0xFF;
    T1CC0L = 0xFF;
    
    EA = 1;
    T1IE = 1;

    T1CTL=0b00001110;
}


